package org.jeecg.modules.cosmetic.handler;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.modules.cosmetic.constant.enums.FixedMaterialEnum;
import org.jeecg.modules.cosmetic.constant.enums.IecicLinkedTypeEnum;
import org.jeecg.modules.cosmetic.constant.enums.IsDeleteEnum;
import org.jeecg.modules.cosmetic.constant.enums.ReferencesEnum;
import org.jeecg.modules.cosmetic.entity.*;
import org.jeecg.modules.cosmetic.model.dto.*;
import org.jeecg.modules.cosmetic.model.vo.FormulaExcelVO;
import org.jeecg.modules.cosmetic.model.vo.FormulaModifyVO;
import org.jeecg.modules.cosmetic.model.vo.FormulaProductListVO;
import org.jeecg.modules.cosmetic.model.vo.FormulaSaveVO;
import org.jeecg.modules.cosmetic.service.*;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author scx
 * @date 2022/6/30 3:48 下午
 */
@Service
@Slf4j
public class FormulaHandler {

    @Resource
    private ITProductService productService;
    @Resource
    private ITProductMaterialService productMaterialService;
    @Resource
    private ITRawmaterialService rawmaterialService;
    @Resource
    private ITFormulaService formulaService;
    @Resource
    private ITIecicLinkedService iecicLinkedService;
    @Resource
    private ITSecurityEvaluationService securityEvaluationService;
    @Resource
    private ITFormulaRiskService formulaRiskService;
    @Resource
    private ITReferencesService referencesService;
    @Resource
    private ITCheckFormulaService checkFormulaService;
    @Resource
    private ITRiskService riskService;
    @Resource
    private ITMaterialLinkedService materialLinkedService;
    @Value("${formula.special.substr}")
    private String specialSubstr;

    /**
     * @param multipartRequest
     * @return org.jeecg.common.api.vo.Result<org.jeecg.modules.cosmetic.model.dto.FormulaDTO>
     * @description 导入excel模版
     */
    @Transactional
    @DS("cosmetic-datasource")
    public Result<FormulaDTO> importFormulaExcel(MultipartHttpServletRequest multipartRequest, Long oldProductId) throws Exception {

        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();

        for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
            // 获取上传文件对象
            MultipartFile file = entity.getValue();
            ImportParams params = new ImportParams();
            params.setTitleRows(1);
            params.setHeadRows(1);
            params.setNeedSave(true);
            List<FormulaExcelVO> formulaExcelVOS = ExcelImportUtil.importExcel(file.getInputStream(), FormulaExcelVO.class, params);
            checkExcelParams(formulaExcelVOS);
            Long productId = productService.createProduct(oldProductId);
            if (productId == null || productId == 0) {
                log.error("配方模版数据导入-创建产品失败");
                return Result.error("导入数据失败");
            }
            AtomicReference<Long> parentId = new AtomicReference<>(0L);
            List<TFormula> formulas = formulaExcelVOS.stream().map(m -> {
                TFormula formula = new TFormula();
                formula.setProductId(productId);
                if (m.getIdxNum() == null || StringUtils.isEmpty(m.getMaterialContent())) {
                    formula.setParentId(parentId.get());
                }
                formula.setCnName(m.getCnName());
                formula.setIncicName(m.getIncicName());
                formula.setIngredientContent(m.getIngredientContent());
                formula.setMajorUsePurpose(m.getMajorUsePurpose());
                formula.setMaterialContent(m.getMaterialContent());
                formula.setActualContent(m.getActualContent());
                //查询原料信息
                TRawmaterial tRawmaterial = rawmaterialService.getOne(new LambdaQueryWrapper<TRawmaterial>()
                        .eq(TRawmaterial::getCnName, m.getCnName())
                        .eq(TRawmaterial::getIncicName, m.getIncicName())
                        .eq(TRawmaterial::getStatus, IsDeleteEnum.NORMAL.getCode()));
                Optional.ofNullable(tRawmaterial).orElseThrow(() -> new JeecgBootException("当前料物质异常，请重试！"));
                formula.setIecicNum(tRawmaterial.getIecicNum());
                formula.setIecicId(tRawmaterial.getId());

                //检验是否有技术规范要求
                if (StringUtils.isNotBlank(tRawmaterial.getCstsInfo()) && tRawmaterial.getCstsInfo().contains(specialSubstr)) {
                    formula.setRemark(tRawmaterial.getCstsInfo().substring(0, tRawmaterial.getCstsInfo().indexOf(specialSubstr)));
                }

                formulaService.save(formula);
                //判断是否需要更新存储父类ID
                if (m.getIdxNum() != null && StringUtils.isNotEmpty(m.getMaterialContent())) {
                    parentId.set(formula.getId());
                }
                return formula;
            }).collect(Collectors.toList());
            return Result.OK(getResultData(productId, formulas, true));
        }
        return Result.error("导入配方模版失败");
    }

    /**
     * @param vo
     * @return java.util.List<org.jeecg.modules.cosmetic.model.dto.FormulaProductDTO>
     * @description 查询配方产品列表
     */
    public List<FormulaProductDTO> queryFormulaProductList(FormulaProductListVO vo) {
        List<TCheckFormula> tCheckFormulas = checkFormulaService.list(new LambdaQueryWrapper<TCheckFormula>()
                .eq(TCheckFormula::getStatus, IsDeleteEnum.NORMAL.getCode())
                .likeRight(TCheckFormula::getTitle, vo.getQueryKey())
                .last("limit 5"));
        if (CollectionUtils.isEmpty(tCheckFormulas)) {
            return Lists.newArrayList();
        }

        return tCheckFormulas.stream().map(m -> {
            FormulaProductDTO formulaProductDTO = new FormulaProductDTO();
            formulaProductDTO.setId(m.getId());
            formulaProductDTO.setCnName(m.getTitle());
            return formulaProductDTO;
        }).collect(Collectors.toList());
    }


    /**
     * @param formulaSaveVO
     * @return org.jeecg.common.api.vo.Result<org.jeecg.modules.cosmetic.model.dto.FormulaDTO>
     * @description 根据产品ID保存配方
     */
    @Transactional
    @DS("cosmetic-datasource")
    public Result<FormulaDTO> saveFormulaByProductId(FormulaSaveVO formulaSaveVO) {
        TCheckFormula checkFormula = checkFormulaService.getById(formulaSaveVO.getId());
        Optional.ofNullable(checkFormula).orElseThrow(() -> new JeecgBootException("产品信息不存在"));
        if (Objects.equals(checkFormula.getStatus(), IsDeleteEnum.DELETE.getCode())) {
            log.error("当前产品已删除，id：{}", formulaSaveVO.getId());
            return Result.error("产品信息不存在");
        }
        if (StringUtils.isEmpty(checkFormula.getInputJson())) {
            return Result.error("当前料物质信息为空");
        }
        List<FormulaJsonDTO> formulaJsonDTOS = JSON.parseArray(checkFormula.getInputJson(), FormulaJsonDTO.class);
        if (CollectionUtils.isEmpty(formulaJsonDTOS)) {
            throw new JeecgBootException("配方成分信息JSON解析异常");
        }
        Long productId = productService.createProduct(formulaSaveVO.getProductId());
        if (productId == null || productId == 0) {
            log.error("配方模版数据导入-创建产品失败");
            return Result.error("导入数据失败");
        }
        List<TFormula> formulas = formulaJsonDTOS.stream().map(m -> {
            //参数校验
            checkElementParams(m);
            TFormula formula = new TFormula();
            formula.setProductId(productId);
            formula.setCnName(m.getCn_name());
            formula.setIncicName(m.getIncic_name());
            formula.setIngredientContent("100");
            formula.setMaterialContent(m.getInput_value());
            formula.setActualContent(m.getInput_value());
            //查询原料信息
            TRawmaterial tRawmaterial = rawmaterialService.getOne(new LambdaQueryWrapper<TRawmaterial>()
                    .eq(TRawmaterial::getIecicNum, m.getIecic_num())
                    .eq(TRawmaterial::getStatus, IsDeleteEnum.NORMAL.getCode()));
            Optional.ofNullable(tRawmaterial).orElseThrow(() -> new JeecgBootException("当前料物质异常，请重试！"));
            formula.setIecicNum(tRawmaterial.getIecicNum());
            formula.setIecicId(tRawmaterial.getId());

            //检验是否有技术规范要求
            if (StringUtils.isNotEmpty(tRawmaterial.getCstsInfo()) && tRawmaterial.getCstsInfo().contains(specialSubstr)) {
                formula.setRemark(tRawmaterial.getCstsInfo().substring(0, tRawmaterial.getCstsInfo().indexOf(specialSubstr)));
            }
            formulaService.save(formula);
            return formula;
        }).collect(Collectors.toList());
        return Result.OK(getResultData(productId, formulas, true));
    }

    /**
     * @param productId
     * @return java.util.List<org.jeecg.modules.cosmetic.entity.TFormula>
     * @description 查询产品原料列表
     */
    public List<TFormula> queryFormulaList(Long productId) {
        List<TFormula> formulas = formulaService.list(new LambdaQueryWrapper<TFormula>()
                .eq(TFormula::getProductId, productId));
        if (CollectionUtils.isEmpty(formulas)) {
            log.error("产品配方信息不存在，productId:{}", productId);
            return Lists.newArrayList();
        }
        return formulas;
    }

    /**
     * @param formulaModifyVO
     * @return org.jeecg.common.api.vo.Result<java.lang.Void>
     * @description 修改配方信息
     */
    public Result<Void> modifyFormulaByPrimary(FormulaModifyVO formulaModifyVO) {
        TFormula formula = formulaService.getById(formulaModifyVO.getId());
        Optional.ofNullable(formula).orElseThrow(() -> new JeecgBootException("产品配方信息不存在"));
        if (StringUtils.isNotBlank(formulaModifyVO.getMajorUsePurpose())) {
            formula.setMajorUsePurpose(formulaModifyVO.getMajorUsePurpose());
        }
        if (StringUtils.isNotBlank(formulaModifyVO.getRemark())) {
            formula.setRemark(formulaModifyVO.getRemark());
        }
        boolean flag = formulaService.updateById(formula);
        return flag ? Result.OK() : Result.error("修改失败");
    }


    /**
     * 根据产品主键查询配方信息
     *
     * @param productId
     * @return
     */
    public Result<FormulaDTO> getFormulaDataByProductId(Long productId) {
        List<TFormula> tFormulas = formulaService.list(new LambdaQueryWrapper<TFormula>()
                .eq(TFormula::getProductId, productId));
        if (CollectionUtils.isEmpty(tFormulas)) {
            return Result.error("产品配方信息不存在");
        }
        return Result.OK(getResultData(productId, tFormulas, false));
    }

    /**
     * @param productId
     * @param formulas
     * @param initData
     * @return org.jeecg.modules.cosmetic.model.dto.FormulaDTO
     * @description 统一返回数据
     */
    private FormulaDTO getResultData(Long productId, List<TFormula> formulas, boolean initData) {
        BigDecimal materialContentTotal = formulas.stream()
                .filter(f -> StringUtils.isNotBlank(f.getMaterialContent()))
                .map(m -> new BigDecimal(m.getMaterialContent())).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, 2);
        BigDecimal actualContentTotal = formulas.stream()
                .filter(f -> StringUtils.isNotBlank(f.getActualContent()))
                .map(m -> new BigDecimal(m.getActualContent())).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, 2);

        //配方百分比不足百的ID集合
        Set<Long> ids = Sets.newHashSet();
        formulas.stream().filter(f -> f.getParentId() == null || f.getParentId() == 0).map(m -> {
            BigDecimal sonIngredientContent = formulas.stream()
                    .filter(fs -> fs.getParentId() != null && Objects.equals(fs.getParentId(), m.getId()))
                    .map(ms -> new BigDecimal(ms.getIngredientContent())).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (sonIngredientContent.compareTo(BigDecimal.ZERO) == 0) {
                return null;
            }
            BigDecimal mixTotal = sonIngredientContent.add(new BigDecimal(m.getIngredientContent()));
            if (mixTotal.compareTo(BigDecimal.valueOf(100)) != 0) {
                return m.getId();
            }
            return null;
        }).filter(nf -> !ObjectUtil.isEmpty(nf)).collect(Collectors.toCollection(() -> ids));

        List<FormulaDTO.ElementDTO> elementDTOS = formulas.stream().map(m -> {
            FormulaDTO.ElementDTO elementDTO = new FormulaDTO.ElementDTO();
            BeanUtils.copyProperties(m, elementDTO);
            return elementDTO;
        }).collect(Collectors.toList());

        //初始化数据
        if (initData) {
            syncDataInit(productId, formulas);
        }
        return FormulaDTO.builder()
                .materialContentTotal(String.valueOf(materialContentTotal))
                .actualContentTotal(String.valueOf(actualContentTotal))
                .productId(productId)
                .elementList(elementDTOS)
                .compoundId(ids)
                .build();
    }

    /**
     * @param productId
     * @param formulas
     * @description 异步初始化相关数据
     */
    @Async
    @Transactional
    @DS("cosmetic-datasource")
    public void syncDataInit(Long productId, List<TFormula> formulas) {
        log.info("配方数据异步初始化===========");
        if (CollectionUtils.isEmpty(formulas)) {
            log.error("配方数据为空，数据初始化失败");
            return;
        }

        AtomicInteger count = new AtomicInteger(0);
        //查询原料关联风险物质初始化
        IntStream.range(0, formulas.size()).forEach(i -> {
            TFormula formula = formulas.get(i);
            formula.setIdxNum(formula.getParentId() != null ? count.get() : count.incrementAndGet());
            formulaService.updateById(formula);

            List<TIecicLinked> iecicLinkeds = iecicLinkedService.list(new LambdaQueryWrapper<TIecicLinked>()
                    .eq(TIecicLinked::getIecicId, formula.getIecicId())
                    .eq(TIecicLinked::getType, IecicLinkedTypeEnum.RISK.getType())
                    .eq(TIecicLinked::getStatus, IsDeleteEnum.NORMAL.getCode()));
            if (CollectionUtils.isEmpty(iecicLinkeds)) {
                log.error("该原料未关联风险物质：{}", formula.getCnName());
                return;
            }
            iecicLinkeds.forEach(riskLink -> {
                TRisk risk = riskService.getById(riskLink.getLinkedId());
                if (ObjectUtil.isEmpty(risk)) {
                    return;
                }
                TFormulaRisk formulaRisk = new TFormulaRisk();
                formulaRisk.setFormulaId(formula.getId());
                formulaRisk.setRiskName(risk.getName());
                formulaRisk.setRemark(risk.getHarmIntro());
                formulaRiskService.save(formulaRisk);
            });
        });

        List<IngredientMaterialLinkedDTO> list = Lists.newArrayList();
        //原料成分去重并把相同成分的实际含量相加
        formulas.stream().collect(Collectors.toMap(TFormula::getIecicNum, a -> a, (o1, o2) -> {
            BigDecimal actualContent = new BigDecimal(o1.getActualContent()).add(new BigDecimal(o2.getActualContent()));
            o1.setActualContent(String.valueOf(actualContent));
            return o1;
        })).values().forEach(item -> {
            TSecurityEvaluation securityEvaluation = new TSecurityEvaluation();
            securityEvaluation.setProductId(item.getProductId());
            securityEvaluation.setIecicNum(item.getIecicNum());
            securityEvaluation.setCnName(item.getCnName());
            securityEvaluation.setContent(item.getActualContent());

            //固定原料更新评估结论
            String msg = FixedMaterialEnum.getFixedMaterialEnumBySerial(item.getIecicNum());
            if (StringUtils.isNotEmpty(msg)) {
                securityEvaluation.setEvaluatedConclusion(msg);
            }
            securityEvaluationService.save(securityEvaluation);

            //查询原料关联文献
            List<TIecicLinked> iecicLinkeds = iecicLinkedService.list(new LambdaQueryWrapper<TIecicLinked>()
                    .eq(TIecicLinked::getIecicId, item.getIecicId())
                    .eq(TIecicLinked::getType, IecicLinkedTypeEnum.CIR.getType())
                    .eq(TIecicLinked::getStatus, IsDeleteEnum.NORMAL.getCode()));
            if (CollectionUtils.isEmpty(iecicLinkeds)) {
                log.error("该原料未关联相关文献：{}", item.getCnName());
                return;
            }

            //成分ID->文献ID 通过对象IngredientMaterialLinkedDTO存入list
            iecicLinkeds.stream()
                    .filter(f -> Objects.equals(f.getType(), IecicLinkedTypeEnum.CIR.getType()))
                    .map(TIecicLinked::getLinkedId)
                    .collect(Collectors.toSet()).stream().map(m -> IngredientMaterialLinkedDTO.builder()
                    .securityValuationId(securityEvaluation.getId())
                    .content(securityEvaluation.getContent())
                    .referenceId(m)
                    .build()).collect(Collectors.toCollection(() -> list));
        });

        if (CollectionUtils.isEmpty(list)) {
            log.error("没有关联相关参考文献信息");
            return;
        }

        //根据成分含量排序生成文献序号
        List<IngredientMaterialLinkedDTO> finalList = list.stream()
                .sorted((o1, o2) -> new BigDecimal(o2.getContent()).compareTo(new BigDecimal(o1.getContent()))).collect(Collectors.toList());

        IntStream.range(0, finalList.size()).forEach(i -> {
            IngredientMaterialLinkedDTO dto = finalList.get(i);
            TProductMaterial productMaterial = new TProductMaterial();
            productMaterial.setProductId(productId);
            productMaterial.setIdxNum(i + 1);

            TReferences references = referencesService.getById(dto.getReferenceId());
            if (ObjectUtil.isNotEmpty(references)) {
                productMaterial.setName(references.getReferencesName());
                productMaterial.setReferencesId(references.getId());
            }
            productMaterial.setType(ReferencesEnum.DOC.getType());
            productMaterial.setStatus(IsDeleteEnum.NORMAL.getCode());
            productMaterialService.save(productMaterial);

            //插入成分文献关联信息
            TMaterialLinked materialLinked = new TMaterialLinked();
            materialLinked.setMaterialId(productMaterial.getId());
            materialLinked.setLinkedId(dto.getSecurityValuationId());
            materialLinked.setLinkedType(IecicLinkedTypeEnum.CIR.getType());
            materialLinkedService.save(materialLinked);
        });
    }


    /**
     * @param formulaJsonDTO
     * @description 校验成分参数
     */
    private void checkElementParams(FormulaJsonDTO formulaJsonDTO) {
        if (StringUtils.isEmpty(formulaJsonDTO.getCn_name())) {
            throw new JeecgBootException("有原料标准中文名称为空");
        }
        if (StringUtils.isEmpty(formulaJsonDTO.getIncic_name())) {
            throw new JeecgBootException("有原料INCIC名称/英文名称为空。");
        }
        if (StringUtils.isEmpty(formulaJsonDTO.getInput_value())) {
            throw new JeecgBootException("有原料实际成分含量百分比为空。");
        }
    }


    /**
     * @param formulaExcelVOS
     * @description excel参数校验
     */
    private void checkExcelParams(List<FormulaExcelVO> formulaExcelVOS) {
        for (int i = 0; i < formulaExcelVOS.size(); i++) {
            int lineNumber = i + 1;
            FormulaExcelVO formulaExcelVO = new FormulaExcelVO();
            BeanUtils.copyProperties(formulaExcelVOS.get(i), formulaExcelVO);
            if (StringUtils.isEmpty(formulaExcelVO.getCnName())) {
                throw new JeecgBootException("第 " + lineNumber + " 行：标准中文名称为空。");
            }
            if (StringUtils.isEmpty(formulaExcelVO.getIncicName())) {
                throw new JeecgBootException("第 " + lineNumber + " 行：INCIC名称/英文名称为空。");
            }
            if (StringUtils.isEmpty(formulaExcelVO.getIngredientContent())) {
                throw new JeecgBootException("第 " + lineNumber + " 行：原料中成分含量为空。");
            }
            if (StringUtils.isEmpty(formulaExcelVO.getActualContent())) {
                throw new JeecgBootException("第 " + lineNumber + " 行：实际成分含量为空。");
            }
        }
    }

}
